package Util;


import enums.EncryptTypeEnum;
import enums.ReturnValue;

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;

/**
 * 加密，签名工具类
 */
public class EncryptUtils {
    //region RSA 加密签名
    //region RSA签名

    /**
     * RSA签名
     *
     * @param privateKeyStr 秘钥字符串
     * @param data          源数据
     * @param charset       字符集编码
     * @return ReturnValue
     */
    public static ReturnValue makeRSASign(String privateKeyStr, String data, String charset) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(privateKeyStr)) {
            retValue.setMessage("签名秘钥为空");
            return retValue;
        }
        if (StringUtils.isEmpty(data)) {
            retValue.setMessage("签名数据为空");
            return retValue;
        }
        if (StringUtils.isEmpty(charset)) {
            charset = "utf-8";
        }
        retValue = restorePrivateKey(privateKeyStr, EncryptTypeEnum.RSA);
        if (!retValue.getSuccess()) {
            retValue.setMessage("签名失败：" + retValue.getMessage());
            return retValue;
        }

        PrivateKey privateKey = (PrivateKey) retValue.getData();
        if (privateKey == null) {
            retValue.setSuccess(false);
            retValue.setMessage("签名失败：privKey null");
            return retValue;
        }
        byte[] signBytes;
        try {
            Signature sign = Signature.getInstance("SHA1withRSA");
            sign.initSign(privateKey);
            sign.update(data.getBytes(charset));
            signBytes = sign.sign();
        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setMessage("签名失败：" + ex.getMessage());
            retValue.setSuccess(false);
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(true);
        retValue.setMessage("签名成功");
        retValue.setData(StringUtils.bytesToBase64(signBytes));
        return retValue;
    }
    //endregion

    //region RSA验签

    /**
     * RSA验签
     *
     * @param publicKeyStr 公钥字符串
     * @param data         明文
     * @param sign         签名
     * @param charset      字符编码
     * @return ReturnValue
     */
    public static ReturnValue verifyRSASign(String publicKeyStr, String data, String sign, String charset) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(publicKeyStr)) {
            retValue.setMessage("签名秘钥为空");
            return retValue;
        }
        if (StringUtils.isEmpty(data)) {
            retValue.setMessage("签名数据为空");
            return retValue;
        }
        if (StringUtils.isEmpty(sign)) {
            retValue.setMessage("签名为空");
            return retValue;
        }
        if (StringUtils.isEmpty(charset)) {
            charset = "utf-8";
        }
        retValue = restorePublicKey(publicKeyStr, EncryptTypeEnum.RSA);
        if (!retValue.getSuccess()) {
            retValue.setMessage("验签失败：" + retValue.getMessage());
            return retValue;
        }

        PublicKey publicKey = (PublicKey) retValue.getData();
        if (publicKey == null) {
            retValue.setSuccess(false);
            retValue.setMessage("验签失败 pubKey null");
            return retValue;
        }

        boolean signedSuccess;
        try {
            Signature verifySign = Signature.getInstance("SHA1withRSA");
            verifySign.initVerify(publicKey);
            verifySign.update(data.getBytes(charset));
            signedSuccess = verifySign.verify(Base64.getDecoder().decode(sign));

        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setMessage("验签失败：" + ex.getMessage());
            retValue.setSuccess(false);
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(signedSuccess);
        retValue.setData(null);
        if (signedSuccess) {
            retValue.setMessage("签名验证通过");
        } else {
            retValue.setMessage("签名验证无效");
        }

        return retValue;
    }
    //endregion

    //region RSA2签名

    /**
     * RSA2签名
     *
     * @param privateKeyStr 秘钥字符串
     * @param data          源数据
     * @param charset       字符集编码
     * @return ReturnValue
     */
    public static ReturnValue makeRSA2Sign(String privateKeyStr, String data, String charset) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(privateKeyStr)) {
            retValue.setMessage("签名秘钥为空");
            return retValue;
        }
        if (StringUtils.isEmpty(data)) {
            retValue.setMessage("签名数据为空");
            return retValue;
        }
        if (StringUtils.isEmpty(charset)) {
            charset = "utf-8";
        }
        retValue = restorePrivateKey(privateKeyStr, EncryptTypeEnum.RSA2);
        if (!retValue.getSuccess()) {
            retValue.setMessage("签名失败：" + retValue.getMessage());
            return retValue;
        }

        PrivateKey privateKey = (PrivateKey) retValue.getData();
        if (privateKey == null) {
            retValue.setSuccess(false);
            retValue.setMessage("签名失败：privKey null");
            return retValue;
        }
        byte[] signed;
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(data.getBytes(charset));
            signed = sign.sign();
        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setMessage("签名失败：" + ex.getMessage());
            retValue.setSuccess(false);
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(true);
        retValue.setMessage("签名成功");
        retValue.setData(StringUtils.bytesToBase64(signed));
        return retValue;
    }
    //endregion

    //region RSA2验签

    /**
     * RSA2验签
     *
     * @param publicKeyStr 公钥字符串
     * @param data         明文
     * @param sign         签名
     * @param charset      字符编码
     * @return ReturnValue
     */
    public static ReturnValue verifyRSA2Sign(String publicKeyStr, String data, String sign, String charset) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(publicKeyStr)) {
            retValue.setMessage("签名秘钥为空");
            return retValue;
        }
        if (StringUtils.isEmpty(data)) {
            retValue.setMessage("签名数据为空");
            return retValue;
        }
        if (StringUtils.isEmpty(sign)) {
            retValue.setMessage("签名为空");
            return retValue;
        }
        if (StringUtils.isEmpty(charset)) {
            charset = "utf-8";
        }
        retValue = restorePublicKey(publicKeyStr, EncryptTypeEnum.RSA2);
        if (!retValue.getSuccess()) {
            retValue.setMessage("验签失败：" + retValue.getMessage());
            return retValue;
        }

        PublicKey publicKey = (PublicKey) retValue.getData();
        if (publicKey == null) {
            retValue.setSuccess(false);
            retValue.setMessage("验签失败 pubKey null");
            return retValue;
        }

        boolean signedSuccess;
        try {
            Signature verifySign = Signature.getInstance("SHA256withRSA");
            verifySign.initVerify(publicKey);
            verifySign.update(data.getBytes(charset));
            signedSuccess = verifySign.verify(Base64.getDecoder().decode(sign));

        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setMessage("验签失败：" + ex.getMessage());
            retValue.setSuccess(false);
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(signedSuccess);
        retValue.setData(null);
        if (signedSuccess) {
            retValue.setMessage("签名验证通过");
        } else {
            retValue.setMessage("签名验证无效");
        }

        return retValue;
    }
    //endregion

    //region RSA2加密
    // 最大加密字节数，超出最大字节数需要分组加密
    static int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA 加密
     *
     * @param publicKeyStr 公钥字符串
     * @param data         源数据
     * @param charset      字符编码
     * @return
     */
    public static ReturnValue rsaEncrypt(String publicKeyStr, String data, String charset) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(publicKeyStr)) {
            retValue.setMessage("签名秘钥为空");
            return retValue;
        }
        if (StringUtils.isEmpty(data)) {
            retValue.setMessage("加密数据为空");
            return retValue;
        }
        if (StringUtils.isEmpty(charset)) {
            charset = "utf-8";
        }
        retValue = restorePublicKey(publicKeyStr, EncryptTypeEnum.RSA);
        if (!retValue.getSuccess()) {
            retValue.setMessage("加密失败：" + retValue.getMessage());
            return retValue;
        }

        PublicKey publicKey = (PublicKey) retValue.getData();
        if (publicKey == null) {
            retValue.setSuccess(false);
            retValue.setMessage("加密失败 pubKey null");
            return retValue;
        }
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        byte[] inputArray = data.getBytes();
        int inputLength = inputArray.length;
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            while (inputLength - offSet > 0) {
                if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                    offSet += MAX_ENCRYPT_BLOCK;
                } else {
                    cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                    offSet = inputLength;
                }
                resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
                System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
            }
            String encryptData = StringUtils.bytesToBase64(resultBytes);
            retValue.setData(encryptData);
        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setSuccess(false);
            retValue.setMessage("加密失败：" + ex.getMessage());
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(true);
        retValue.setMessage("加密成功");
        return retValue;
    }
    //endregion

    //region RSA2解密
    static int MAX_DECRYPT_BLOCK = 256;

    /**
     * RSA 解密
     *
     * @param privateKeyStr 私钥字符串
     * @param data          加密数据
     * @param charset       字符编码
     * @return ReturnValue
     */
    public static ReturnValue rsaDecrypt(String privateKeyStr, String data, String charset) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(privateKeyStr)) {
            retValue.setMessage("签名秘钥为空");
            return retValue;
        }
        if (StringUtils.isEmpty(data)) {
            retValue.setMessage("签名数据为空");
            return retValue;
        }
        if (StringUtils.isEmpty(charset)) {
            charset = "utf-8";
        }
        retValue = restorePrivateKey(privateKeyStr, EncryptTypeEnum.RSA);
        if (!retValue.getSuccess()) {
            retValue.setMessage("解密失败：" + retValue.getMessage());
            return retValue;
        }
        PrivateKey privateKey = (PrivateKey) retValue.getData();
        if (privateKey == null) {
            retValue.setSuccess(false);
            retValue.setMessage("解密失败 privKey null");
            return retValue;
        }
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        byte[] inputArray = null;
        try {
            inputArray = Base64.getDecoder().decode(data);
        } catch (Exception ex) {
            retValue.setMessage("解密目标数据格式不正确，不是Base64编码格式");
            retValue.setSuccess(false);
            return retValue;
        }
        if (inputArray == null) {
            retValue.setMessage("解密目标数据格式不正确，不是Base64编码格式！");
            retValue.setSuccess(false);
            return retValue;
        }
        int inputLength = inputArray.length;
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            while (inputLength - offSet > 0) {
                if (inputLength - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(inputArray, offSet, MAX_DECRYPT_BLOCK);
                    offSet += MAX_DECRYPT_BLOCK;
                } else {
                    cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                    offSet = inputLength;
                }
                resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
                System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
            }
            String decryptData = new String(resultBytes, charset);
            retValue.setData(decryptData);
        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setSuccess(false);
            retValue.setMessage("解密失败：" + ex.getMessage());
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(true);
        retValue.setMessage("解密成功");
        return retValue;
    }




    //region restorePublicKey 公钥字符串转换成公钥对象

    /**
     * 还原公钥
     *
     * @param publicKeyStr 公钥字符串
     * @param encryptType  加密类型
     * @return ReturnValue
     */
    private static ReturnValue restorePublicKey(String publicKeyStr, EncryptTypeEnum encryptType) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(publicKeyStr)) {
            retValue.setMessage("公钥字符串为空");
            return retValue;
        }
        byte[] publicKeyBytes = null;
        try {
            publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
        } catch (Exception ex) {
            retValue.setMessage("公钥字符格式不正确");
            return retValue;
        }
        if (publicKeyBytes == null) {
            retValue.setMessage("公钥字符格式不正确！");
            return retValue;
        }

        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        PublicKey publicKey;
        try {
            KeyFactory factory;
            switch (encryptType) {
                case RSA:
                case RSA2:
                    factory = KeyFactory.getInstance("RSA");
                    break;
                case ECDSA:
                    factory = KeyFactory.getInstance("EC");
                    break;
                default:
                    retValue.setSuccess(false);
                    retValue.setMessage("加密类型不正确");
                    return retValue;
            }
            publicKey = factory.generatePublic(x509EncodedKeySpec);
        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setSuccess(false);
            retValue.setMessage("还原公钥失败：" + ex.getMessage());
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(true);
        retValue.setMessage("执行成功");
        retValue.setData(publicKey);
        return retValue;
    }
    //endregion

    //region restorePrivateKey 私钥字符串转换成私钥对象

    /**
     * 还原私钥
     *
     * @param privateKeyStr 私钥字符串
     * @param encryptType   加密类型
     * @return ReturnValue
     */
    private static ReturnValue restorePrivateKey(String privateKeyStr, EncryptTypeEnum encryptType) {
        ReturnValue retValue = new ReturnValue();
        retValue.setSuccess(false);
        if (StringUtils.isEmpty(privateKeyStr)) {
            retValue.setMessage("私钥字符串为空");
            return retValue;

        }
        byte[] privateKeyBytes = null;
        try {
            privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
        } catch (Exception ex) {
            retValue.setMessage("私钥字符格式不正确");
            return retValue;
        }
        if (privateKeyBytes == null) {
            retValue.setMessage("私钥字符格式不正确！");
            return retValue;
        }
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        PrivateKey privateKey;
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        try {
            KeyFactory factory;
            switch (encryptType) {
                case RSA:
                case RSA2:
                    factory = KeyFactory.getInstance("RSA");
                    break;
                case ECDSA:
                    factory = KeyFactory.getInstance("EC");
                    break;
                default:
                    retValue.setSuccess(false);
                    retValue.setMessage("加密类型不正确");
                    return retValue;
            }
            privateKey = factory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception ex) {
            ex.printStackTrace();
            retValue.setSuccess(false);
            retValue.setMessage("还原私钥失败：" + ex.getMessage());
            retValue.setException(ex);
            return retValue;
        }
        retValue.setSuccess(true);
        retValue.setMessage("执行成功");
        retValue.setData(privateKey);
        return retValue;
    }
    //endregion
}

